/**
 * This file is part of jFlvTool.
 *
 * jFlvTool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * jFlvTool is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * file name  : TagReader.java
 * authors    : Jon Keys
 * created    : June 28, 2007, 3:52 PM
 * copyright  : Sony Digital Authoring Services
 *
 * modifications:
 * Date:        Name:           Description:
 * ----------   --------------- ----------------------------------------------
 * June 28, 2007    Jon Keys         Creation
 */

package com.sonydadc.dw.jflv.metadata;

import java.util.ArrayList;
import java.util.HashMap;

import com.sonydadc.dw.jflv.parse.TagParser;
import com.sonydadc.dw.jflv.io.IOHelper;
import com.sonydadc.dw.jflv.io.FileWriter;
import com.sonydadc.dw.jflv.tags.*;

/**
 *
 * @author Jon Keys
 */
public class TagBroker {

    private double frameRate;
    private int startingByteOffset;
    private TagStor stor;
    private IOHelper ioh;
    private FlvHeader flvh;

    private ArrayList<TagStor> tags;
    private TagParser tp;

    /**
     * Creates a new instance of TagBroker
     */
    public TagBroker(IOHelper ioh, FlvHeader flvh) {
        frameRate = 0;
        stor = null;
        this.startingByteOffset = flvh.getDataOffset() + 4;
        tp = new TagParser(ioh);
        tp.setTotalByteOffset(startingByteOffset);
        tp.readTags();
        tags = tp.getTags();
        this.ioh = ioh;
        this.flvh = flvh;
    }

    public long getDuration(){

        long dur = 0;

        if(flvh.hasVideo()){
            dur = tags.get(tp.getLastVideoTag()).getTimestamp();
        }else if(flvh.hasAudio()){
            dur = tags.get(tp.getLastAudioTag()).getTimestamp();
        }else{
			if (tags.size() > 0)
				dur = tags.get(tags.size()-1).getTimestamp();
        }

        return dur;

    }//getDuration()

    public void writeTags(){
        FileWriter fw = ioh.getFileWriter(flvh);
        fw.setDebug(ioh.isDebug());
        fw.setTags(tags);
        fw.setInStream(ioh.getFileReader().getStream());
        flvh.setHasAudio((tp.getLastAudioTag() != -1));
        flvh.setHasVideo((tp.getLastVideoTag() != -1));
        fw.writeTags();
    }

    public void recalcKeyFrameTagOffsets(){

        ArrayList<Double> keyFrameTagOffsets = new ArrayList<Double>();
        long totalByteOffset = startingByteOffset;
        int totMetaBytes = 0;

        for(TagStor ts : tags){

            if(ts.getType() == FlvTag.VIDEO){

                if(((VideoTag)ts.getTag()).getFrameType() == VideoTag.KEYFRAME){
                    keyFrameTagOffsets.add(new Double(totalByteOffset));
                }

            }else if(ts.getType() == FlvTag.META){

                if(ts.isNew()){
                    totMetaBytes += ts.getDataSize() +15;
                }else{
                    totMetaBytes += ts.getDataSize();
                }

            }//else

            totalByteOffset += ts.getDataSize() +15;

        }//for

        tp.setKeyFrameTagOffsets(keyFrameTagOffsets);
        tp.setTotalByteOffset(totalByteOffset);
        tp.setTotalMetaSize(totMetaBytes);

    }//recalcTagOffsets()

    public void addTags(ArrayList<TagStor> tags2add, boolean keepFrameRate, boolean overwrite){

        frameRate = tp.getFrameseq().getFrameRate();

        if(keepFrameRate && frameRate == 0){

            System.out.println("Error adding new tags: current tag doesn't fit into existing framerate");
            return;

        }else if(keepFrameRate){

            for(TagStor ts : tags2add){

                if(ts.getTimestamp() % (1000 / frameRate) != 0){
                    System.out.println("Error adding new tags: current tag doesn't fit into existing framerate");
                    return;
                }

            }//for

        }//else

        int pos = 0;

        for(TagStor ts : tags2add){

            TagStor nextTag = findNextTag(ts.getTimestamp());

            if(nextTag == null){
                tags.add(ts);
                //System.out.println("added new tag to end");
                continue;
            }

            pos = tags.indexOf(nextTag);

            if(ts.getTimestamp() == nextTag.getTimestamp() && ts.getType() == nextTag.getType()){

                //IF type = meta AND (the new.meta.event != next.meta.event OR can overwrite)
                if(ts.getType() == FlvTag.META && (!((MetaTag)ts.getTag()).getEvent().equals(((MetaTag)nextTag.getTag()).getEvent()) || !overwrite)){

                    tags.add(pos,ts);
                    //System.out.println("inserted tag at : " + pos);
                    tp.setTags(tags);
                    pushTags(pos);

                }else{

                    //System.out.println("replaced tag at : " + pos);
                    tags.remove(pos);
                    tags.add(pos,ts);
                    tp.setTags(tags);

                }//else

            }else{

                tags.add(pos,ts);
                //System.out.println("inserted tag at : " + pos);
                tp.setTags(tags);
                pushTags(pos);

            }//else

        }//for

    }//addTags()

    private void pushTags(int pos){

        if(pos <= tp.getLastAudioTag()){
            tp.setLastAudioTag(tp.getLastAudioTag()+1);
        }
        if(pos <= tp.getLastVideoTag()){
            tp.setLastVideoTag(tp.getLastVideoTag()+1);
        }
        if(pos <= tp.getFirstVideoTag()){
            tp.setFirstVideoTag(tp.getFirstVideoTag()+1);
        }
        if(pos <= tp.getFirstAudioTag()){
            tp.setFirstAudioTag(tp.getFirstAudioTag()+1);
        }
        if(pos <= tp.getOnMetaTag()){
            tp.setOnMetaTag(tp.getOnMetaTag()+1);
        }

    }//pushTags()

    private TagStor findNextTag(long timestamp){

        TagStor nextTag = null;

        for(int i=0;i<tags.size();i++){

            if(tags.get(i).getTimestamp() >= timestamp){
                nextTag = tags.get(i);
                break;
            }

        }//for

        return nextTag;

    }//findNextTag()

    public int getVideoWidth(){

        int vwidth = 0;
		
		if (tp.getFirstVideoTag() < 0)
				return vwidth;
			
        if(((VideoTag)tags.get(tp.getFirstVideoTag()).getTag()).getWidth() == 0 && tp.getOnMetaTag() != -1){

            try{
                HashMap<String,Object> mdata = (HashMap<String,Object>)(((MetaTag)tags.get(tp.getOnMetaTag()).getTag()).getMetaData());
                vwidth = ((Double)mdata.get("width")).intValue();
            }catch(Exception e){
                //do nothing
            }

        }else{

            vwidth = ((VideoTag)tags.get(tp.getFirstVideoTag()).getTag()).getWidth();

        }//else

        return vwidth;

    }//getVideoWidth()

    public int getVideoHeight(){

        int vheight = 0;

		if (tp.getFirstVideoTag() < 0)
				return vheight;
			
        if(((VideoTag)tags.get(tp.getFirstVideoTag()).getTag()).getHeight() == 0 && tp.getOnMetaTag() != -1){

            try{
                HashMap<String,Object> mdata = (HashMap<String,Object>)(((MetaTag)tags.get(tp.getOnMetaTag()).getTag()).getMetaData());
                vheight = ((Double)mdata.get("height")).intValue();
            }catch(Exception e){
                //do nothing
            }

        }else{

            vheight = ((VideoTag)tags.get(tp.getFirstVideoTag()).getTag()).getHeight();

        }//else

        return vheight;

    }//getVideoHeight()

    public long getTotalDataSize(){
        return (tp.getTotalVideoSize() + tp.getTotalAudioSize() + tp.getTotalMetaSize());
    }

    public float getVideoDataRate(float duration){

        float videoDataRate = 0;

        if(tp.getTotalVideoSize() !=0 ){
            videoDataRate = tp.getVideoDataSize() / duration * 8 / 1000;
        }

        return videoDataRate;

    }//getVideoDataRate()

    public float getAudioDataRate(float duration){

        float audioDataRate = 0;

        if(tp.getTotalAudioSize() !=0 ){
           audioDataRate = tp.getAudioDataSize() / duration * 8 / 1000;
        }

        return audioDataRate;

    }//getAudioDataRate()

    public double getLastKeyFrameTimeStamp(){

        double lkfts = 0;
		
		if (tp.getKeyFrameTagTimes().size() > 0) {
			double lkfTime = (tp.getKeyFrameTagTimes().get(tp.getKeyFrameTagTimes().size()-1)).doubleValue();

			if(lkfTime !=0){
				lkfts = lkfTime;
			}
		}
        return lkfts;

    }//getLastKeyFrameTimeStamp()

    public int getAudioCodecId(){
        return ((AudioTag)tags.get(tp.getFirstAudioTag()).getTag()).getSoundFormat();
    }

    public int getVideoCcodecId(){
        return ((VideoTag)tags.get(tp.getFirstVideoTag()).getTag()).getCodecId();
    }

    public long getAudioDelay(){

        long audioDelay = 0;
        if(tp.getFirstVideoTag() != -1){
            audioDelay = tags.get(tp.getFirstVideoTag()).getTimestamp() / 1000;
        }

        return audioDelay;

    }//getAudioDelay()

    public boolean canSeekToEnd(){
        return (((VideoTag)tags.get(tp.getLastVideoTag()).getTag()).getFrameType() == VideoTag.KEYFRAME);
    }

    public boolean isStero(){
        return (((AudioTag)tags.get(tp.getFirstAudioTag()).getTag()).getSoundType() == AudioTag.STEREO);
    }

    public int getAudioSampleRate(){
        return ((AudioTag)tags.get(tp.getFirstAudioTag()).getTag()).getSoundRate();
    }

    public int getAudioSampleSize(){
        return ((AudioTag)tags.get(tp.getFirstAudioTag()).getTag()).getSoundSampleSize();
    }

    public ArrayList<TagStor> getTags() {
        return tags;
    }

    public double getFrameRate() {
        return this.frameRate;
    }

    public long getTotalAudioSize() {
        return tp.getTotalAudioSize();
    }

    public long getTotalVideoSize() {
        return tp.getTotalVideoSize();
    }

    public long getTotalMetaSize() {
        return tp.getTotalMetaSize();
    }

    public long getTotalByteOffset() {
        return tp.getTotalByteOffset();
    }

    public AMFObject getKeyFrameTags() {

        AMFObject keyFrameTags = new AMFObject();
        keyFrameTags.put("times", tp.getKeyFrameTagTimes());
        keyFrameTags.put("filepositions", tp.getKeyFrameTagOffsets());
        return keyFrameTags;

    }//getKeyFrameTags()

    public ArrayList<Object> getCuePointTags() {
        return tp.getCuePointTags();
    }

    public int getLastAudioTag(){
        return tp.getLastAudioTag();
    }

    public int getLastVideoTag(){
        return tp.getLastVideoTag();
    }

}//TagBroker
